﻿/////////////////////////////////////////////////////////////////////////////////////////////////
//
// IL2C - A translator for ECMA-335 CIL/MSIL to C language.
// Copyright (c) 2016-2019 Kouji Matsui (@kozy_kekyo, @kekyo2)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
/////////////////////////////////////////////////////////////////////////////////////////////////

using System;

using Mono.Cecil.Cil;

using IL2C.Translators;

namespace IL2C.ILConverters
{
    internal static class LogicalConverterUtilities
    {
        public enum BinaryOperators
        {
            And,
            Or,
            Xor
        }

        public static ExpressionEmitter Prepare(BinaryOperators binaryOperator, DecodeContext decodeContext)
        {
            var si1 = decodeContext.PopStack();
            var si0 = decodeContext.PopStack();

            char opChar;
            switch (binaryOperator)
            {
                case BinaryOperators.And: opChar = '&'; break;
                case BinaryOperators.Or: opChar = '|'; break;
                case BinaryOperators.Xor: opChar = '^'; break;
                default: throw new Exception();
            }

            // See also: ECMA-335: III.1.5 Operand type table - Integer Operations

            // Int32 = (Int32) op (Int32)
            if (si0.TargetType.IsInt32StackFriendlyType && si1.TargetType.IsInt32StackFriendlyType)
            {
                var result = decodeContext.PushStack(decodeContext.PrepareContext.MetadataContext.Int32Type);
                return (extractContext, _) => new[] { string.Format(
                    "{0} = {1} {2} {3}", extractContext.GetSymbolName(result), extractContext.GetSymbolName(si0), opChar, extractContext.GetSymbolName(si1)) };
            }

            // Int64 = (Int64) op (Int64)
            if (si0.TargetType.IsInt64StackFriendlyType && si1.TargetType.IsInt64StackFriendlyType)
            {
                var result = decodeContext.PushStack(decodeContext.PrepareContext.MetadataContext.Int64Type);
                return (extractContext, _) => new[] { string.Format(
                    "{0} = {1} {2} {3}", extractContext.GetSymbolName(result), extractContext.GetSymbolName(si0), opChar, extractContext.GetSymbolName(si1)) };
            }

            // IntPtr = (Int32|IntPtr) op (Int32|IntPtr)
            if ((si0.TargetType.IsInt32StackFriendlyType || si0.TargetType.IsIntPtrStackFriendlyType) &&
                (si1.TargetType.IsInt32StackFriendlyType || si1.TargetType.IsIntPtrStackFriendlyType))
            {
                var result = decodeContext.PushStack(decodeContext.PrepareContext.MetadataContext.IntPtrType);
                return (extractContext, _) => new[] { string.Format(
                    "{0} = (intptr_t){1} {2} (intptr_t){3}", extractContext.GetSymbolName(result), extractContext.GetSymbolName(si0), opChar, extractContext.GetSymbolName(si1)) };
            }

            throw new InvalidProgramSequenceException(
                "Unknown logical operation: Location={0}, Op={1}, Type0={2}, Type1={3}",
                decodeContext.CurrentCode.RawLocation,
                binaryOperator,
                si0.TargetType.FriendlyName,
                si1.TargetType.FriendlyName);
        }
    }

    internal sealed class AndConverter : InlineNoneConverter
    {
        public override OpCode OpCode => OpCodes.And;

        public override ExpressionEmitter Prepare(
            DecodeContext decodeContext)
        {
            return LogicalConverterUtilities.Prepare(
                LogicalConverterUtilities.BinaryOperators.And, decodeContext);
        }
    }

    internal sealed class OrConverter : InlineNoneConverter
    {
        public override OpCode OpCode => OpCodes.Or;

        public override ExpressionEmitter Prepare(
            DecodeContext decodeContext)
        {
            return LogicalConverterUtilities.Prepare(
                LogicalConverterUtilities.BinaryOperators.Or, decodeContext);
        }
    }

    internal sealed class XorConverter : InlineNoneConverter
    {
        public override OpCode OpCode => OpCodes.Xor;

        public override ExpressionEmitter Prepare(
            DecodeContext decodeContext)
        {
            return LogicalConverterUtilities.Prepare(
                LogicalConverterUtilities.BinaryOperators.Xor, decodeContext);
        }
    }
}
